Skip to content

#11681 - Enable -Wshadow-field and investigate actual bugs it finds - #11684

Merged
mitchute merged 15 commits into
developfrom
Wshadow-field
Aug 10, 2026
Merged

#11681 - Enable -Wshadow-field and investigate actual bugs it finds#11684
mitchute merged 15 commits into
developfrom
Wshadow-field

Conversation

@jmarrec

@jmarrec jmarrec commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Pull request overview

Description of the purpose of this PR

Enable -Wshadow-field on clang and fixup the warnings. Found a few fixable bugs.

Found another one that will warrant another issue in HeatPumpAirToWater, for now I restored the behavior to the current so I wouldn't produce diff, but it seems that there are more than a few issues.

Pull Request Author

  • Title of PR should be user-synopsis style (clearly understandable in a standalone changelog context)
  • Label the PR with at least one of: Defect, Refactoring, NewFeature, Performance, and/or DoNoPublish
  • Pull requests that impact EnergyPlus code must also include unit tests to cover enhancement or defect repair
  • Author should provide a "walkthrough" of relevant code changes using a GitHub code review comment process
  • If any diffs are expected, author must demonstrate they are justified using plots and descriptions
  • If changes fix a defect, the fix should be demonstrated in plots and descriptions
  • If any defect files are updated to a more recent version, upload new versions here or on DevSupport
  • If IDD requires transition, transition source, rules, ExpandObjects, and IDFs must be updated, and add IDDChange label
  • If structural output changes, add to output rules file and add OutputChange label
  • If adding/removing any LaTeX docs or figures, update that document's CMakeLists file dependencies
  • If adding/removing any output files (e.g., eplustbl.*)
    • Update ..\scripts\Epl-run.bat
    • Update ..\scripts\RunEPlus.bat
    • Update ..\src\EPLaunch\ MainModule.bas, epl-ui.frm, and epl.vbp (VersionComments)
    • Update ...github\workflows\energyplus.py

Reviewer

  • Perform a Code Review on GitHub
  • If branch is behind develop, merge develop and build locally to check for side effects of the merge
  • If defect, verify by running develop branch and reproducing defect, then running PR and reproducing fix
  • If feature, test running new feature, try creative ways to break it
  • CI status: all green or justified
  • Check that performance is not impacted (CI Linux results include performance check)
  • Run Unit Test(s) locally
  • Check any new function arguments for performance impacts
  • Verify IDF naming conventions and styles, memos and notes and defaults
  • If new idf included, locally check the err file and other outputs

@jmarrec jmarrec self-assigned this Jul 10, 2026
@jmarrec jmarrec added Defect Includes code to repair a defect in EnergyPlus Developer Issue Related to cmake, packaging, installers, or developer tooling (CI, etc) labels Jul 10, 2026
Comment thread cmake/CompilerFlags.cmake
target_compile_options(project_options INTERFACE $<$<CONFIG:RelWithDebInfo>:-UNDEBUG>)
target_compile_options(project_fp_options INTERFACE -ffp-contract=off) # Disable fused-floating point operations (default is fast)
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
target_compile_options(project_warnings INTERFACE -Wshadow-field) # Equivalent to MSVC's C4458 (declaration of 'identifier' hides class member); narrower than -Wshadow

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New flag on clang. Like I said, -Wshadow is way too noisy

};

struct SteamBaseboardDesignData : SteamBaseboardParams
struct SteamBaseboardDesignData

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wshadow revealed a big inheritance mistake in SteamBaseboardRadiator

So there’s a ZoneHVAC:Baseboard:RadiantConvective:Steam and a ZoneHVAC:Baseboard:RadiantConvective:Steam:Design objects

The C++ SteamBaseboardParams instance has an int index to find the SteamBaseboardDesignData...

But the SteamBaseboardDesignData inherits from SteamBaseboardParams, so it carries EVERY field from SteamBaseboardParams, so about 656 bytes for no reason!

It shouldn't inherit at all!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

};

struct HWBaseboardDesignData : HWBaseboardParams
struct HWBaseboardDesignData

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as the Steam counterpart


// logic flags
bool oneTimeInitFlag = true;
bool oneTimeInitFlagPLHP = true;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oneTimeInitFlag is defined in PlantComponent, which is inherits, but we need that flag here as it's used to call SetupOutputVariables

Comment on lines +494 to +496
std::array<int, maxNumSpeeds + 1> capFuncTempCurveIndices = {};
std::array<int, maxNumSpeeds + 1> powerRatioFuncTempCurveIndices = {};
std::array<int, maxNumSpeeds + 1> powerRatioFuncPLRCurveIndices = {};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EIRPlantLoopHeatPump defines int indices

struct HeatPumpAirToWater : public EIRPlantLoopHeatPump would redefine it: it shadows with a different type. Not great.

Ideally we add a different Base but this was too much change for not so much gains, so I just defined a different one.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's fine, too.

Comment on lines +462 to +468
// Deliberately NOT named companionHeatPumpCoil: the base class's own EIRPlantLoopHeatPump::companionHeatPumpCoil
// must stay null for HeatPumpAirToWater objects, because EIRPlantLoopHeatPump::sizeLoadSide() (and other base
// sizing methods) branch on it being non-null to size off a companion coil using logic that was written for,
// and only checks for, the plain PlantLoopHeatPump:EIR:Heating/Cooling pair (DataPlant::PlantEquipmentType::
// HeatPumpEIRHeating/Cooling), not HeatPumpAirToWater. This member is used instead by
// HeatPumpAirToWater::pairUpCompanionCoils() and HeatPumpAirToWater::calcOpMode().
HeatPumpAirToWater *companionAWHPCoil = nullptr;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a hard bug. Here I'm voluntarily restoring the behavior to avoid a diff.

But there's a problem in there.

Needs further investigation (separate, pre-existing issue, not caused by this branch).

HeatPumpAirToWater::sizeLoadSide() is not virtual, and its only would-be caller (EIRPlantLoopHeatPump::onInitLoopEquip) invokes this->sizeLoadSide(state) from within the base class's own scope, so that call always statically resolves to EIRPlantLoopHeatPump::sizeLoadSide,
never the derived override.
Confirmed no production or test code calls sizeLoadSide() through a HeatPumpAirToWater*-typed pointer either, so HeatPumpAirToWater::sizeLoadSide() (and its referenceCapacityOneUnit
recompute) is effictively dead code today. Left a TODO comment at the definition;

This likely needs a real fix (e.g. making sizeLoadSide virtual, or overriding onInitLoopEquip in HeatPumpAirToWater) but that's a behavior (and diff-producing) change out of scope for this PR.

@jmarrec jmarrec Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rraustad FYI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AWHP sizeLoadSide function was added here but I don't think it's used anywhere. As @jmarrec says, it's the EIRPlantLoopHeatPump::sizeLoadSide that gets called to size the plant HP. referenceCapacityOneUnit is used only internally to this model and initialized here in AWHP getInput. I suspect that this is not working as expected (i.e., if ratedCapacity is autosized then the value of referenceCapacityOneUnit is also autosize).

if (thisAWHP.ratedCapacity[thisAWHP.numSpeeds - 1] == DataSizing::AutoSize) {
    thisAWHP.referenceCapacityWasAutoSized = true;
}
thisAWHP.referenceCapacityOneUnit = thisAWHP.ratedCapacity[thisAWHP.numSpeeds - 1];
thisAWHP.referenceCapacity = thisAWHP.referenceCapacityOneUnit * thisAWHP.heatPumpMultiplier;

I have a personal requirement of only using autosized inputs in new example files for this very reason, to test all pertinent code.

The object used in PlantLoopHeatPump_EIR_AirSource_and_AWHP.idf:

HeatPump:AirToWater,
  test_AWHP,               !- Name
  20000,                   !- Rated Heating Capacity at Speed 1 {W}
  40000,                   !- Rated Heating Capacity at Speed 2 {W}

HeatPump:AirToWater,
   N25, \field Rated Heating Capacity at Speed 1
        \autosizable
        \default autosize
   N27, \field Rated Heating Capacity at Speed 2
        \autosizable
        \default autosize

And then used like this in calcOpMode, which would not work well if these input fields are autosized.

    auto availableCapacityOneUnit = this->referenceCapacityOneUnit * capacityModifierFuncTemp;

The test would be to autosize these 2 inputs and watch for smoke.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for documenting it. This can be treated separately.

Comment on lines +2840 to 2848
// TODO: sizeLoadSide() is not virtual, and the only production call site (EIRPlantLoopHeatPump::onInitLoopEquip,
// via `this->sizeLoadSide(state);`) is compiled in the base class's own scope, so it always statically resolves to
// EIRPlantLoopHeatPump::sizeLoadSide. This override is therefore unreachable dead code today; the
// referenceCapacityOneUnit recompute below never runs. Pre-existing issue, unrelated to the Wshadow-field cleanup.
void HeatPumpAirToWater::sizeLoadSide(EnergyPlusData &state)
{
EIRPlantLoopHeatPump::sizeLoadSide(state);
this->referenceCapacityOneUnit = this->referenceCapacity / this->heatPumpMultiplier;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is effectively dead currently, which I suppose isn't the intent given this function does post-calculations...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (this->companionHeatPumpCoil != nullptr) {

In EIRPlantLoopHeatPump::sizeLoadSide, the companionHeatingCoil was ALWAYS nullptr for HeatPumpAirToWater (and it is still, but it's more explicit now).

HeatPumpAirToWater would initialize its own shadowing copy of companionHeatPumpCoil and leave the base one unitialized.

And it's the Base method that's called so in there companionHeatPumpCoil is definitely the Base's, not the Derived

I have a not so MCVE at https://compiler-explorer.com/z/3vYjEj1oY that shows it, and a more MCVE (less complete) at https://gcc.godbolt.org/z/YrMY9P3q8

Real64 Temperature_PrevIteration = 0.0; // C
Real64 Temperature_PrevTimeStep = 0.0; // C
Real64 Beta = 0.0; // K/W
BaseThermalPropertySet Properties;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed BaseThermalPropertySet Properties; from BaseCell, and instead gave each of its three derived structs its own appropriately-typed Properties member directly:

  • RadialCellInformationBaseThermalPropertySet Properties; (new, explicit)
  • CartesianCellBaseThermalPropertySet Properties; (new, explicit)
  • FluidCellInformation → kept its existing ExtendedFluidProperties Properties; (unchanged)

Why: FluidCellInformation was redeclaring Properties with the wider ExtendedFluidProperties type (adds Viscosity/Prandtl) to shadow the inherited BaseThermalPropertySet Properties from BaseCell — needed so that bulk-assignments like cell.PipeCellData.Fluid.Properties = thisCircuit->CurFluidPropertySet; copy the full extended struct instead of object-slicing it. This triggered a -Wshadow-field warning, and — since C++ field-hiding isn't virtual dispatch — meant every FluidCellInformation instance carried a second, entirely dead BaseThermalPropertySet subobject inherited from BaseCell that was never read or written.

Since BaseCell itself is never instantiated or referenced directly (only used as a base for these three structs), moving Properties down into each derived struct removes the shadowing entirely and eliminates the wasted subobject, with no behavior change — RadialCellInformation and CartesianCell still get the same BaseThermalPropertySet Properties they had before, just declared locally instead of inherited

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's also a good catch.

jmarrec added 15 commits August 10, 2026 14:54
/Users/julien/Software/Others/EnergyPlus/third_party/btwxt/include/btwxt/logging.h:33:52: error: parameter 'message' shadows member inherited from type 'CourierrException' [-Werror,-Wshadow-field]
   33 |         explicit BtwxtException(const std::string &message, Courierr::Courierr &logger)
      |                                                    ^
/Users/julien/Software/Others/EnergyPlus/third_party/btwxt/vendor/courierr/include/courierr/courierr.h:66:17: note: declared here
   66 |     std::string message;
      |                 ^
In file included from /Users/julien/Software/Others/EnergyPlus/src/EnergyPlus/api/state.cc:49:
In file included from /Users/julien/Software/Others/EnergyPlus/src/EnergyPlus/Data/CommonIncludes.hh:266:
In file included from /Users/julien/Software/Others/EnergyPlus/src/EnergyPlus/SolarShading.hh:61:
In file included from /Users/julien/Software/Others/EnergyPlus/third_party/penumbra/include/penumbra/penumbra.h:16:
/Users/julien/Software/Others/EnergyPlus/third_party/penumbra/include/penumbra/logging.h:40:49: error: parameter 'message' shadows member inherited from type 'CourierrException' [-Werror,-Wshadow-field]
   40 |   explicit PenumbraException(const std::string &message, Courierr::Courierr &logger)
      |                                                 ^
/Users/julien/Software/Others/EnergyPlus/third_party/btwxt/vendor/courierr/include/courierr/courierr.h:66:17: note: declared here
   66 |     std::string message;
51ba69
Removed `BaseThermalPropertySet Properties;` from `BaseCell`, and instead gave each of its three derived structs its own appropriately-typed `Properties` member directly:
- `RadialCellInformation` → `BaseThermalPropertySet Properties;` (new, explicit)
- `CartesianCell` → `BaseThermalPropertySet Properties;` (new, explicit)
- `FluidCellInformation` → kept its existing `ExtendedFluidProperties Properties;` (unchanged)

**Why:** `FluidCellInformation` was redeclaring `Properties` with the wider `ExtendedFluidProperties` type (adds `Viscosity`/`Prandtl`) to shadow the inherited `BaseThermalPropertySet Properties` from `BaseCell` — needed so that bulk-assignments like `cell.PipeCellData.Fluid.Properties = thisCircuit->CurFluidPropertySet;` copy the full extended struct instead of object-slicing it. This triggered a `-Wshadow-field` warning, and — since C++ field-hiding isn't virtual dispatch — meant every `FluidCellInformation` instance carried a second, entirely dead `BaseThermalPropertySet` subobject inherited from `BaseCell` that was never read or written.

Since `BaseCell` itself is never instantiated or referenced directly (only used as a base for these three structs), moving `Properties` down into each derived struct removes the shadowing entirely and eliminates the wasted subobject, with no behavior change — `RadialCellInformation` and `CartesianCell` still get the same `BaseThermalPropertySet Properties` they had before, just declared locally instead of inherited.
So there’s a ZoneHVAC:Baseboard:RadiantConvective:Steam and a ZoneHVAC:Baseboard:RadiantConvective:Steam:Design objects

The C++ `SteamBaseboardParams` instance has an `int` index to find the `SteamBaseboardDesignData`...

But the `SteamBaseboardDesignData` inherits from `SteamBaseboardParams`, so it carries EVERY field from SteamBaseboardParams, so about 656 bytes for no reason!

It shouldn't inherit at all!
… inheriting

Here HWBaseboardParams::HeatingCapMethod/ScaledHeatingCapacity are genuinely used as per-instance cached copies (set once from the design object, read every   timestep), so those stayed untouched
Removed four pure-duplicate member redeclarations that shadowed identical inherited fields for no reason — same name, same type, same default, no divergent usage found anywhere in the `.cc`:

- `EIRPlantLoopHeatPump::oneTimeInitFlag` (line 209) — exact duplicate of `PlantComponent::oneTimeInitFlag` (`bool`, default `true`). Deleted; `this->oneTimeInitFlag` now resolves to the inherited one, same behavior.
- `EIRFuelFiredHeatPump::flowMode` — exact duplicate of `EIRPlantLoopHeatPump::flowMode` (`DataPlant::FlowMode`, default `Invalid`). Deleted.
- `EIRFuelFiredHeatPump::capModFTErrorIndex`, `eirModFTErrorIndex`, `eirModFPLRErrorIndex` — exact duplicates of the same-named `int` error-index members on `EIRPlantLoopHeatPump` (all default `0`). Deleted.

In all four cases the derived class had no custom constructor initializing these differently, and every usage site accessed them polymorphically through `this->` — so removing the redeclaration doesn't change behavior, just stops the derived object from carrying (and the compiler from having to reason about) two separately-named-but-identical copies of the same state.
…en it goes to an array for HeatPumpAirToWater

The base class xxxFuncYYYCurveIndex still exists but it's left untouched at int = 0, so trying to access curves(int) with it will throw when NDEBUG not defined, and having three unused ints beats creating a new derived class...
…in there

Fix regression in HeatPumpAirToWater sizing from companionHeatPumpCoil rename

The Wshadow-field fix in 4f15f0b removed HeatPumpAirToWater's own `companionHeatPumpCoil` shadow member and had its pairUpCompanionCoils() assign into the base EIRPlantLoopHeatPump::companionHeatPumpCoil instead.
That member is also read by EIRPlantLoopHeatPump::sizeLoadSide() (and other base sizing methods), whose companion-based sizing branch was written for, and only type-checks against, the plain PlantLoopHeatPump:EIR
Heating/Cooling pair (DataPlant::PlantEquipmentType::HeatPumpEIRHeating/Cooling) -- it was never adapted for HeatPumpAirToWater.
Before the rename, this branch was structurally unreachable for AWHP objects (the base member always stayed null for them); after the rename it started firing, changing autosized "Rated Water Volume Flow Rate in Heating Mode" for PlantLoopHeatPump_EIR_AirSource_and_AWHP.eio from 0.005 to 0.018 m3/s.

Fixing it: Restore a separate, non-shadowing member (companionAWHPCoil) used only by  `HeatPumpAirToWater::pairUpCompanionCoils()`/`calcOpMode()`, leaving the base member's null-for-AWHP behavior intact, matching pre-fix sizing results.

Needs further investigation (separate, pre-existing issue, not caused by this branch).

`HeatPumpAirToWater::sizeLoadSide()` is not virtual, and its only would-be caller (EIRPlantLoopHeatPump::onInitLoopEquip) invokes `this->sizeLoadSide(state)` from within the base class's own scope, so that call always statically resolves to `EIRPlantLoopHeatPump::sizeLoadSide`,
never the derived override.
Confirmed no production or test code calls `sizeLoadSide()` through a HeatPumpAirToWater*-typed pointer either, so `HeatPumpAirToWater::sizeLoadSide()` (and its referenceCapacityOneUnit
recompute) is effictively dead code today. Left a TODO comment at the definition;

This likely needs a real fix (e.g. making sizeLoadSide virtual, or overriding onInitLoopEquip in HeatPumpAirToWater) but that's a behavior (and diff-producing) change out of scope for this PR.
@jmarrec
jmarrec marked this pull request as ready for review August 10, 2026 14:47

@mitchute mitchute left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jmarrec. Looks good.

};

struct SteamBaseboardDesignData : SteamBaseboardParams
struct SteamBaseboardDesignData

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

Real64 Temperature_PrevIteration = 0.0; // C
Real64 Temperature_PrevTimeStep = 0.0; // C
Real64 Beta = 0.0; // K/W
BaseThermalPropertySet Properties;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's also a good catch.

Comment on lines +494 to +496
std::array<int, maxNumSpeeds + 1> capFuncTempCurveIndices = {};
std::array<int, maxNumSpeeds + 1> powerRatioFuncTempCurveIndices = {};
std::array<int, maxNumSpeeds + 1> powerRatioFuncPLRCurveIndices = {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's fine, too.

Comment on lines +462 to +468
// Deliberately NOT named companionHeatPumpCoil: the base class's own EIRPlantLoopHeatPump::companionHeatPumpCoil
// must stay null for HeatPumpAirToWater objects, because EIRPlantLoopHeatPump::sizeLoadSide() (and other base
// sizing methods) branch on it being non-null to size off a companion coil using logic that was written for,
// and only checks for, the plain PlantLoopHeatPump:EIR:Heating/Cooling pair (DataPlant::PlantEquipmentType::
// HeatPumpEIRHeating/Cooling), not HeatPumpAirToWater. This member is used instead by
// HeatPumpAirToWater::pairUpCompanionCoils() and HeatPumpAirToWater::calcOpMode().
HeatPumpAirToWater *companionAWHPCoil = nullptr;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for documenting it. This can be treated separately.

@mitchute
mitchute merged commit 46fa6c7 into develop Aug 10, 2026
9 of 10 checks passed
@mitchute
mitchute deleted the Wshadow-field branch August 10, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Defect Includes code to repair a defect in EnergyPlus Developer Issue Related to cmake, packaging, installers, or developer tooling (CI, etc)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable Wshadow-field and investigate actual bugs it finds

3 participants